
-------------------------------------------------------------------------------
--
-- File: tb_TestDataPathCalib.vhd
-- Author: Tudor Gherman, Robert Bocos
-- Original Project: ZmodScopeController
-- Date: 11 Dec. 2020
--
-------------------------------------------------------------------------------
-- (c) 2020 Copyright Digilent Incorporated
-- All Rights Reserved
-- 
-- This program is free software; distributed under the terms of BSD 3-clause 
-- license ("Revised BSD License", "New BSD License", or "Modified BSD License")
--
-- Redistribution and use in source and binary forms, with or without modification,
-- are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice, this
--    list of conditions and the following disclaimer.
-- 2. Redistributions in binary form must reproduce the above copyright notice,
--    this list of conditions and the following disclaimer in the documentation
--    and/or other materials provided with the distribution.
-- 3. Neither the name(s) of the above-listed copyright holder(s) nor the names
--    of its contributors may be used to endorse or promote products derived
--    from this software without specific prior written permission.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
-- ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
-- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
-- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
-- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
-- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--
-------------------------------------------------------------------------------
--
-- This test bench instantiates the DataPath and ADC_Calibration modules of the
-- ZmodScopeController. A ramp signal is used to simulate the ADC data
-- and a data checker compares the output of the DataPath module against the 
-- expected data (generated by CalibDataReference) and generates an error 
-- message if a mismatch occurs.
--  
-------------------------------------------------------------------------------

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use ieee.numeric_std.all;
use work.PkgZmodDigitizer.all;

entity tb_TestDataPathCalib is
   Generic (
      -- ADC number of bits
      kADC_Width : integer range 10 to 16 := 14;
      -- Sampling Clock Period in ns; 
      kSamplingPeriod : real range 2.5 to 100.0:= 8.138;
      -- kSimTestMode generic allows the test bench to instantiate the
      -- ADC_Calibration modules to be instantiated either in test mode
      -- or in normal operation.
      kSimTestMode : std_logic := '0';
      -- ADC dynamic/static calibration 
      kExtCalibEn : boolean := false;
      -- Channel1 low gain multiplicative (gain) compensation coefficient parameter
      kCh1LgMultCoefStatic : std_logic_vector (17 downto 0) := "010001101010110010";
      -- Channel1 low gain additive (offset) compensation coefficient parameter 
      kCh1LgAddCoefStatic : std_logic_vector (17 downto 0) := "111111101111010101";
      -- Channel1 high gain multiplicative (gain) compensation coefficient parameter
      kCh1HgMultCoefStatic : std_logic_vector (17 downto 0) := "010001101010110010"; 
      -- Channel1 high gain additive (offset) compensation coefficient parameter 
      kCh1HgAddCoefStatic : std_logic_vector (17 downto 0) := "111111101111010101"; 
      -- Channel2 low gain multiplicative (gain) compensation coefficient parameter
      kCh2LgMultCoefStatic : std_logic_vector (17 downto 0) := "010001101010110010";
      -- Channel2 low gain additive (offset) compensation coefficient parameter  
      kCh2LgAddCoefStatic : std_logic_vector (17 downto 0) := "111111101111010101"; 
      -- Channel2 high gain multiplicative (gain) compensation coefficient parameter
      kCh2HgMultCoefStatic : std_logic_vector (17 downto 0) := "010000000000000000"; 
      -- Channel2 high gain additive (offset) compensation coefficient parameter 
      kCh2HgAddCoefStatic : std_logic_vector (17 downto 0) := "111111101111010101" 
   );
end tb_TestDataPathCalib;

architecture Behavioral of tb_TestDataPathCalib is
 
constant kNumClockCycles : integer := 3*(2**14); 

signal SysClk100 : std_logic := '0';
signal rZmodDcoPLL_Lock : std_logic;
signal asRst_n : std_logic := '0';
signal asRst : std_logic := '1';
signal ZmodDcoClk, ZmodDcoClkDly : std_logic := '0';
signal DcoClkOut : std_logic := '0';
signal adoRst_n : std_logic := '0';
signal adoRst : std_logic := '1';
signal diZmodADC_Data, diZmodADC_DataDly : std_logic_vector(kADC_Width-1 downto 0) := (others => '0');
signal ZmodDataSel : std_logic_vector (2 downto 0);
signal diZmodADC_DataCnt : unsigned (kADC_Width-1 downto 0);
signal doDataValid, doDataCalibValid : std_logic;
signal doChannelA, doChannelB : std_logic_vector(kADC_Width-1 downto 0);
signal doChannel1_Test, doChannel2_Test : std_logic_vector(kADC_Width-1 downto 0);
signal doChA_TestDly, doChB_TestDly : std_logic_vector(kADC_Width-1 downto 0);
signal doTestMode : std_logic;
signal doCh1Calib, doCh2Calib : std_logic_vector(15 downto 0);
signal doCh1OutInt, doCh2OutInt : integer;
signal doCh1TestInt, doCh2TestInt : integer;
signal doCh1Diff, doCh2Diff : integer;

signal doExtCh1LgMultCoef : std_logic_vector(17 downto 0);
signal doExtCh1LgAddCoef : std_logic_vector(17 downto 0);
signal doExtCh1HgMultCoef : std_logic_vector(17 downto 0);
signal doExtCh1HgAddCoef : std_logic_vector(17 downto 0);
signal doExtCh2LgMultCoef : std_logic_vector(17 downto 0);
signal doExtCh2LgAddCoef : std_logic_vector(17 downto 0);
signal doExtCh2HgMultCoef : std_logic_vector(17 downto 0);
signal doExtCh2HgAddCoef : std_logic_vector(17 downto 0);

signal diDataGenCntEn, diDataGenRst_n : std_logic;
signal doEnableAcquisition : std_logic;
signal doDataAcceptanceReady : std_logic;--This signal would be equivalent to a doDataAxisTready given by an AXI Stream Slave

constant kADC_SamplingClkPeriod : time := 8.138ns;
constant RefClkPeriod : time := 10ns;
constant kVal1 : std_logic_vector (15 downto 0) := x"AAAA";
constant kVal2 : std_logic_vector (15 downto 0) := x"5555";
constant kValMin : std_logic_vector (15 downto 0) := x"8000"; 
constant kValMax : std_logic_vector (15 downto 0) := x"7FFF";
-- Calibration constants used to test the dynamic calibration behavior
constant kCh1LgMultCoefDynamic : std_logic_vector (17 downto 0) := "010000110101100101";
constant kCh1LgAddCoefDynamic : std_logic_vector (17 downto 0) := "111111101111011011";
constant kCh1HgMultCoefDynamic : std_logic_vector (17 downto 0) := "010001101000010001";  
constant kCh1HgAddCoefDynamic : std_logic_vector (17 downto 0) := "111111101110111000";  
constant kCh2LgMultCoefDynamic : std_logic_vector (17 downto 0) := "010000101001111010";  
constant kCh2LgAddCoefDynamic : std_logic_vector (17 downto 0) := "000000000000010000"; 
constant kCh2HgMultCoefDynamic : std_logic_vector (17 downto 0) := "010001011010101111"; 
constant kCh2HgAddCoefDynamic : std_logic_vector (17 downto 0) := "000000001000000111";   
         
begin
                 
InstDataPath : entity work.DataPath
Generic Map(
   kSamplingPeriod => kSamplingPeriod,
   kADC_Width => kADC_Width
)  
Port Map( 
        RefClk => SysClk100,
        arRst => asRst,
        adoRst => adoRst,
        DcoClkIn => ZmodDcoClk,
        DcoClkOut => DcoClkOut,
        rDcoMMCM_LockState => rZmodDcoPLL_Lock,
        doEnableAcquisition => doEnableAcquisition,
        diADC_Data => diZmodADC_DataDly,
        doChannelA => doChannelA,
        doChannelB => doChannelB,
        doDataOutValid => doDataValid
);

InstDataPathDlyCh1 : entity work.DataPathLatency
    Generic Map (
        kNumFIFO_Stages => 0,
        kDataWidth => kADC_Width
    )
    Port Map(
        ZmodDcoClk => DcoClkOut,
        ZmodDcoClkDly => ZmodDcoClkDly,
        doDataIn => diZmodADC_DataDly,
        doChA_DataOut => doChA_TestDly, 		
        doChB_DataOut => doChB_TestDly);
                
InstCalibDataRefCh1 : entity work.CalibDataReference 
    Generic Map (
        kWidth => kADC_Width,
        kExtCalibEn => kExtCalibEn, 
        kLgMultCoefStatic => kCh1LgMultCoefStatic,
        kLgAddCoefStatic  => kCh1LgAddCoefStatic,
        kHgMultCoefStatic => kCh1HgMultCoefStatic,
        kHgAddCoefStatic  => kCh1HgAddCoefStatic,
        kInvert => true,
        kLatency => 2,
        kTestLatency => 1 
    )
    Port Map(
        SamplingClk => DcoClkOut,
        cTestMode => doTestMode, 
        cChIn => doChA_TestDly,
        cChOut => doChannel1_Test,
        cExtLgMultCoef => doExtCh1LgMultCoef, 
        cExtLgAddCoef  => doExtCh1LgAddCoef,
        cExtHgMultCoef => doExtCh1HgMultCoef,
        cExtHgAddCoef  => doExtCh1HgAddCoef,
        cGainState => '1' --Force High Gain
        );  

InstCalibDataRefCh2 : entity work.CalibDataReference 
    Generic Map (
        kWidth => kADC_Width,
        kExtCalibEn => kExtCalibEn, 
        kLgMultCoefStatic => kCh2LgMultCoefStatic,
        kLgAddCoefStatic  => kCh2LgAddCoefStatic,
        kHgMultCoefStatic => kCh2HgMultCoefStatic,
        kHgAddCoefStatic  => kCh2HgAddCoefStatic,
        kInvert => false,
        kLatency => 2,
        kTestLatency => 1  
    )
    Port Map(
        SamplingClk => DcoClkOut,
        cTestMode => doTestMode,
        cChIn => doChB_TestDly,
        cChOut => doChannel2_Test,
        cExtLgMultCoef => doExtCh2LgMultCoef, 
        cExtLgAddCoef  => doExtCh2LgAddCoef,
        cExtHgMultCoef => doExtCh2HgMultCoef,
        cExtHgAddCoef  => doExtCh2HgAddCoef,
        cGainState => '1' --Force High Gain
        );
                 
InstCh1ADC_Calibration : entity work.GainOffsetCalib 
    Generic Map(
        kWidth => kADC_Width,
        kExtCalibEn => kExtCalibEn,
        kInvert => true,
        kLgMultCoefStatic => kCh1LgMultCoefStatic,
        kLgAddCoefStatic  => kCh1LgAddCoefStatic,
        kHgMultCoefStatic => kCh1HgMultCoefStatic,
        kHgAddCoefStatic  => kCh1HgAddCoefStatic
    )
    Port Map
    (
        SamplingClk => DcoClkOut,
        acRst_n => adoRst_n,
        cTestMode => doTestMode,
        cDataAcceptanceReady => doDataAcceptanceReady,
        cExtLgMultCoef => doExtCh1LgMultCoef,
        cExtLgAddCoef  => doExtCh1LgAddCoef,
        cExtHgMultCoef => doExtCh1HgMultCoef,
        cExtHgAddCoef  => doExtCh1HgAddCoef,
        cGainState => '1', --Force High Gain
        cDataRaw => doChannelA,
        cDataInValid => doDataValid,
        cCalibDataOut => doCh1Calib,
        cDataCalibValid => doDataCalibValid
    );

InstCh2ADC_Calibration : entity work.GainOffsetCalib
    Generic Map(
        kWidth => kADC_Width,
        kExtCalibEn => kExtCalibEn,
        kInvert => false,
        kLgMultCoefStatic => kCh2LgMultCoefStatic,
        kLgAddCoefStatic  => kCh2LgAddCoefStatic,
        kHgMultCoefStatic => kCh2HgMultCoefStatic,
        kHgAddCoefStatic  => kCh2HgAddCoefStatic
    )
    Port Map
    (
        SamplingClk => DcoClkOut,
        acRst_n => adoRst_n,
        cTestMode => doTestMode,
        cDataAcceptanceReady => doDataAcceptanceReady,
        cExtLgMultCoef => doExtCh2LgMultCoef,
        cExtLgAddCoef  => doExtCh2LgAddCoef,
        cExtHgMultCoef => doExtCh2HgMultCoef,
        cExtHgAddCoef  => doExtCh2HgAddCoef,
        cGainState => '1', --Force High Gain
        cDataRaw => doChannelB,
        cDataInValid => '0', 
        cCalibDataOut => doCh2Calib,
        cDataCalibValid => open --both channels share the same valid signal
    );

doCh1OutInt <= to_integer(signed(doCh1Calib(15 downto 16-kADC_Width)));               
doCh2OutInt <= to_integer(signed(doCh2Calib(15 downto 16-kADC_Width))); 
doCh1TestInt <= to_integer(signed(doChannel1_Test));               
doCh2TestInt <= to_integer(signed(doChannel2_Test));
doCh1Diff <= doCh1OutInt - doCh1TestInt;
doCh2Diff <= doCh2OutInt - doCh2TestInt; 

-- Generate Reference Clock
RefClock: process
begin
    for i in 0 to kNumClockCycles loop
        wait for RefClkPeriod/2;
        SysClk100 <= not SysClk100;
        wait for RefClkPeriod/2;
        SysClk100 <= not SysClk100;
    end loop;
    wait;
  end process; 

-- Generate ZmodDcoClk.
ZmodDcoClkProc: process
begin
    wait for kTdcoMax;
    for i in 0 to kNumClockCycles/2 loop
        wait for kADC_SamplingClkPeriod/2;
        ZmodDcoClk <= not ZmodDcoClk;
        wait for kADC_SamplingClkPeriod/2;
        ZmodDcoClk <= not ZmodDcoClk;
    end loop;
    
    -- Simulate DcoClk loss for 100 samples.
    -- 100 is a random choice, it has no particular meaning.
    wait for kADC_SamplingClkPeriod * 100;
    
        for i in 0 to (kNumClockCycles/2 - 100) loop
        wait for kADC_SamplingClkPeriod/2;
        ZmodDcoClk <= not ZmodDcoClk;
        wait for kADC_SamplingClkPeriod/2;
        ZmodDcoClk <= not ZmodDcoClk;
    end loop;
    wait;
  end process; 
  
  ZmodDcoClkDly <= ZmodDcoClk after
  (IDDR_ClockPhase(kSamplingPeriod)/360.0)*kADC_SamplingClkPeriod;

-- Ramp signal generator
ProcDataGen: process (ZmodDcoClk)  
begin
   if ((adoRst_n = '0') or (diDataGenRst_n = '0')) then
      diZmodADC_DataCnt <= (others => '0');
   elsif (falling_edge(ZmodDcoClk) or rising_edge(ZmodDcoClk)) then
      if (diDataGenCntEn = '1') then
         diZmodADC_DataCnt <= diZmodADC_DataCnt + 1;
      end if;     
   end if;
end process;

-- Mux that allows selecting (simulating) different patterns 
-- on the ADC data interface.
ProcZmodDataMux: process (diZmodADC_DataCnt, ZmodDataSel)
begin
   case (ZmodDataSel) is
   when ("000") =>
      diZmodADC_Data <= kVal1(kADC_Width-1 downto 0);
   when ("001") =>
      diZmodADC_Data <= kVal2(kADC_Width-1 downto 0);
   when ("010") =>   
      diZmodADC_Data <= std_logic_vector(diZmodADC_DataCnt);
   when ("011") =>
      diZmodADC_Data <= kValMin(15 downto 16-kADC_Width);
   when ("100") =>
      diZmodADC_Data <= kValMax(15 downto 16-kADC_Width);
   when others =>
      diZmodADC_Data <= std_logic_vector(diZmodADC_DataCnt);
   end case;
end process;

diZmodADC_DataDly <= diZmodADC_Data after (kADC_SamplingClkPeriod/4);

ProcRefClkDomainStimuli: process
begin
asRst <= '0';
wait;
end process;

adoRst <= not adoRst_n;
    
ProcDcoClkOutDomainStimuli: process
begin
   adoRst_n <= '0';
   doTestMode <= kSimTestMode;
   doExtCh1LgMultCoef <= kCh1LgMultCoefDynamic;
   doExtCh1LgAddCoef <= kCh1LgAddCoefDynamic;
   doExtCh1HgMultCoef <= kCh1HgMultCoefDynamic; 
   doExtCh1HgAddCoef <= kCh1HgAddCoefDynamic;
   doExtCh2LgMultCoef <= kCh2LgMultCoefDynamic;
   doExtCh2LgAddCoef <= kCh2LgAddCoefDynamic;
   doExtCh2HgMultCoef <= kCh2HgMultCoefDynamic; 
   doExtCh2HgAddCoef <= kCh2HgAddCoefDynamic;
   doEnableAcquisition <= '0';
   doDataAcceptanceReady <= '0';
   
   -- Keep the adoRst_n reset asserted for 10 clock cycles.
   wait for 10 * kADC_SamplingClkPeriod;
   -- Modify signals on the falling edge of DcoClkOut.
   wait until falling_edge(DcoClkOut);
   
   adoRst_n <= '1';
   
   doEnableAcquisition <= '1';
   doDataAcceptanceReady <= '1';
   
   -- Optionally the cInitDone signal can be disabled to observe the system behavior.
   -- No sort of automatic testing is carried out for this optional test.
   -- The effect of ADC or relay initialization on the valid signal is tested 
   -- at the top level test bench (tb_TestTop) level. 
   wait until falling_edge(DcoClkOut);
   -- Keep dInitDone low for 500 clock cycles (this number has no specific relevance).
   wait for (500) * kADC_SamplingClkPeriod;
    
   wait;
end process;

ProcDcoDomainStimuli: process
begin
   diDataGenRst_n <= '0';
   diDataGenCntEn <= '0';
   ZmodDataSel <= "000"; 
   -- Keep the acRst_n reset asserted for 10 clock cycles.
   wait for 10 * kADC_SamplingClkPeriod;
   -- Modify signals on the falling edge of ZmodDcoClk.
   wait until falling_edge(ZmodDcoClk);
   diDataGenRst_n <= '1';
   diDataGenCntEn <= '1';
   ZmodDataSel <= "000";
   -- A counter will be used to generate the input test data for the DataPath module.
   -- However, since a 1LSB error is tolerated so that the CalibDataReference can work 
   -- with real (floating point) values, synchronization problems may not be detected.
   -- For this reason, at the beginning of the test 2 values that differ by more than
   -- 1 LSB will be generated. By this means, the test assures that the DataPath and
   -- ADC_Calibration outputs are correctly synchronized with CalibDataReference.
   
   -- To make sure that the synchronization FIFO comes out of reset when the various 
   -- patterns are applied to the input, the process will wait for the data valid 
   -- signal to be asserted.
   
   wait until doDataValid = '1';
   wait until rising_edge(ZmodDcoClk);
   ZmodDataSel <= "000";
   wait until rising_edge(ZmodDcoClk);
   ZmodDataSel <= "001";
   wait until rising_edge(ZmodDcoClk);
   -- Test IP response for minimum negative and maximum positive input
   -- The value will be hold for 100 clock cycles (no specific relevance
   -- for the time this value is held constant).
   ZmodDataSel <= "011";
   wait for  kADC_SamplingClkPeriod*100;  
   wait until rising_edge(ZmodDcoClk);
   ZmodDataSel <= "100";
   wait for  kADC_SamplingClkPeriod*100;   
   wait until rising_edge(ZmodDcoClk);
   ZmodDataSel <= "010";
   
   -- Optionally the dInitDone signal can be disabled to observe the system behavior.
   -- No sort of automatic testing is carried out for this optional test.
   -- The effect of ADC or relay initialization on the valid signal is tested 
   -- at the top level test bench (tb_TestTop) level.
   
   -- Modify signals on the falling edge of ZmodDcoClk.
   wait until falling_edge(ZmodDcoClk);  
   -- Keep dInitDone low for 500 clock cycles (this number has no specific relevance).
   wait for (500) * kADC_SamplingClkPeriod;
 
   wait;
end process;

-- Check the calibration module (ADC_Calbration) outputs against the expected values.

ProcCh1CheckCalibData: process
begin
   wait until rZmodDcoPLL_Lock = '1';
   wait until doCh1TestInt'event or doCh1OutInt'event;
   -- doCh1Diff is generated on the rising edge of DcoClkOut
   -- and checked on the negative edge of DcoClkOut.
   wait until falling_edge(DcoClkOut);
   if (doDataCalibValid = '1') then
      assert (abs(doCh1Diff) < 2)
      report "Calibration error: mismatch between expected data and actual data" & LF & HT & HT &
             "Expected: " & integer'image(to_integer(signed(doChannel1_Test))) & LF & HT & HT &
             "Actual: " & integer'image(doCh1OutInt) & LF & HT & HT &
             "Difference: " & integer'image(doCh1Diff)
      severity ERROR;
   end if;
end process;

ProcCh2CheckCalibData: process
begin
   wait until rZmodDcoPLL_Lock = '1';
   wait until doCh2TestInt'event or doCh2OutInt'event;
   -- doCh2Diff is generated on the rising edge of DcoClkOut
   -- and checked on the negative edge of DcoClkOut.
   wait until falling_edge(DcoClkOut);
   if (doDataCalibValid = '1') then
      assert (abs(doCh2Diff) < 2)
      report "Calibration error: mismatch between expected data and actual data" & LF & HT & HT &
             "Expected: " & integer'image(to_integer(signed(doChannel2_Test))) & LF & HT & HT &
             "Actual: " & integer'image(doCh2OutInt) & LF & HT & HT &
             "Difference: " & integer'image(doCh2Diff)
      severity ERROR;
   end if;
end process;

-- Test DataPathLatency module. The data generated by the DataPath module
-- is expected to be identical to the data generated by the DataPathLatency
-- module. This test is used to validate the DataPathLatency used in the top 
-- level test bench.

ProcDataPathDlyTest: process
begin
   wait until rZmodDcoPLL_Lock = '1';
   wait until doChannelA'event or doChannelB'event or doChB_TestDly'event or doChA_TestDly'event;
   if (doDataValid = '1') then
      wait until falling_edge(DcoClkOut);
      assert ((doChannelA = doChA_TestDly) and (doChannelB = doChB_TestDly))
      report "DataPathLatency synchronization error" & LF & HT & HT
      severity ERROR;
   end if; 
end process;

end Behavioral;
